/*

    Copyright 1992, Stefanos Kiakas. All rights reserved.

    You may not delete this notice.

*/
#define ABS(x) ( x < 0 ? -x : x)     /* macro to calculate absolute value */
#define RANDTARGET ( random() % TARGETS)/* select a random target to attack */
#define RANDLAUNCH ( ( random() % 440 ) + 20 ) /* x pos. of attack missles */
#define PLACECITY(x) ( random() % x )  /* selct position to place city */


#define NULL             0 /* define end of list marker */


#define MAXRADIUS      15.0  /* maximum radius of explosions */
#define TERRAINCOLOUR   0.20

#define TIMEINTERVAL    0.1
#define DELTA_TIME      100
#define RADINC          1.0 /* radius increment of explosion circles */
#define MAXDEFEXP       15  
#define MAXDEFLAUNCH    10  /* maximum number of defensive missiles */
#define MAXATCKMSSLS    10  /* maximum number of attack missiles */

/* define launch coordinates for defensive missiles */
#define LAUNCH_X        250.0  
#define LAUNCH_Y        25.0

#define DISP             3.0

#define CDX             18.0   // center of target from display coordinates

#define CITYBONUS      500    // city saved bonus
#define MSLBONUS        50    // missiles saved bonus
#define MSLDSTRCT      100    // attack missle intercepted ( destroyed ) bonus
#define BONUSMARK     7000    // give bonus city every so many points

#define OLAUNCH_Y     390.0;  /* Y launch position of attack missles */

#define  DX  5.0
#define  DY  5.0

#define H_CURSOR    330
#define W_CURSOR    460
#define Y_CURSOR     50
#define X_CURSOR     20

#define H_TRG  Y_CURSOR + H_CURSOR
#define W_TRG  X_CURSOR + W_CURSOR
#define Y_TRG   Y_CURSOR
#define X_TRG   X_CURSOR


#define MAXNDIFF    2.0      /* maximum difficulty for novice level */
#define MAXEDIFF    2.5      /* maximum difficulty for experienced level */
#define MAXPDIFF    3.0      /* maximum difficulty for pro level */


#define STARTNDIFF  0.8      /* starting difficulty for novice level */
#define STARTEDIFF  0.75     /* starting difficulty for experienced level */
#define STARTPDIFF  0.9      /* starting difficulty for por level */

#define DIFFINCN    0.1      /* increase in difficulty after each attack for novice level */
#define DIFFINCE    0.25     /* increase in difficulty after each attack for experienced level */ 
#define DIFFINCP    0.3      /* increase in difficulty after each attack for pro level */


#define DISPLAYDELAY 50000


#define SCOREXPOS  150
#define SCOREYPOS  250

#import "markLoc.h"
#import "plotTrajectory.h"
#import "scorePhaseCities.h"   
#import "scorePhaseMissiles.h"  

#include <sys/param.h>
#import <libc.h>
#import "BattleView.h"
#import <appkit/appkit.h>
#import <soundkit/Sound.h>



@implementation BattleView

- initFrame:(const NXRect *) frm
{
   char appD[MAXPATHLEN];
   NXPoint  mySpot;   

   [super initFrame:frm ];
   
   [self allocateGState];
   
   gameRunning = FALSE;
   gamePaused = FALSE;
   
   sprintf(appD,"%s/%s",[self appDirectory],"RedAlert.snd");
   
   redAlertSound = [ [Sound alloc] initFromSoundfile:appD ];
   
   cityBitmap = [NXImage findImageNamed:"city3.tiff"];

   launcherBitmap = [NXImage findImageNamed:"mBase.tiff"];
   
          
   cursorImage=[NXCursor newFromImage:[NXImage newFromSection:"targetD3.tiff"]];
   cBounds.origin.x = X_CURSOR;
   cBounds.origin.y = Y_CURSOR;
   cBounds.size.width= W_CURSOR;
   cBounds.size.height = H_CURSOR;
     
   mySpot.x = 8;
   mySpot.y = 7;
   [cursorImage setHotSpot:&mySpot];


   ndiffstart = STARTNDIFF;
   ndiffinc   = DIFFINCN;
   ndiffmax   = MAXNDIFF;

   return self;
}


/* 
   This function was shamelessly copied from the examples provided.
   
*/
- (const char *)appDirectory
{
	char *suffix;
	
	strcpy(appDirectory, NXArgv[0]);
	if (suffix = rindex(appDirectory,'/'))
		*suffix = '\0';
	if (appDirectory) chdir(appDirectory);
	getwd(appDirectory);
	return  appDirectory;
}

- selectExperienced:sender
{
    ndiffinc = DIFFINCE;
    ndiffstart = STARTEDIFF;
    ndiffmax   = MAXEDIFF;
    return self;
}

- selectNovice:sender
{
    ndiffinc = DIFFINCN;
    ndiffstart = STARTNDIFF;
    ndiffmax   = MAXNDIFF;

    return self;
}

- selectPro:sender
{
    ndiffinc = DIFFINCP;
    ndiffstart = STARTPDIFF;
    ndiffmax   = MAXPDIFF;

    return self;
}


- pauseGame:sender
{
    void stepFun();

    if( gameRunning )
    {
       gameRunning = FALSE;
       gamePaused = TRUE;
       DPSRemoveTimedEntry(gameTimer);
    }   
    else if( !gameRunning && gamePaused )
    {
       gameRunning = TRUE;
       gamePaused = FALSE;
       gameTimer =DPSAddTimedEntry(TIMEINTERVAL,&stepFun,self,NX_BASETHRESHOLD);  
    }
    return self;
}

- resetCursorRects
{
    [self addCursorRect:&cBounds cursor:cursorImage];
    return self;
}

- newGame:sender
{
   int i;
   void stepFun();
   unsigned int currentTimeInMs();
   MSLE_NODE_PNTR   cpdm,fpdm;
   MA_NODE_PNTR     cpam,fpam;
   EXP_PNTR         cpe,fpe;
    
   if( !gameRunning && !gamePaused )
   { 
      /* set up current game difficulty levels */
      cdiffstart = ndiffstart;
      cdiffinc = ndiffinc;
      cdiffmax = ndiffmax;
      
      dFactor = cdiffstart;
      phaseOne = TRUE;
      defensiveMissiles.launched = 0;
      defensiveMissiles.next = NULL;
   
      offensiveMissiles.launched = 0;
      offensiveMissiles.next = NULL;

      defensiveExplosions.exploding = 0;
      defensiveExplosions.next = NULL;   

      for( i = 0; i < CITIES; i++)
         if( ( i % 3 )  && ( i %4 ) )
            cityStatus[i] = TRUE;
         else
            cityStatus[i] = FALSE;
      
      // reset number of missiles  
      aTimeBefore = currentTimeInMs();
  
      // select attack positions
      [self selectTargets];
  
      missiles = 40;	  	    
      score = 0;
      lastBonusAt = 0;
      displayBase = TRUE;
      
      [outMissiles setIntValue:missiles];
      [outScore setIntValue:score];
      [window makeKeyAndOrderFront:self];
   
      gameRunning = TRUE;
      gameTimer =DPSAddTimedEntry(TIMEINTERVAL,&stepFun,self,NX_BASETHRESHOLD);
    
      [self display]; 
   }
   else if( gameRunning && !gamePaused )
   {
       gameRunning = FALSE;
       DPSRemoveTimedEntry(gameTimer);
       // remove entries in linked lists
       cpdm = defensiveMissiles.next;
       while( cpdm != NULL )
       {
          fpdm = cpdm;
	  cpdm = cpdm->next;
	  free(fpdm);
       }
       
       /* remove attack missiles from list, and free memory */ 
       cpam = offensiveMissiles.next;
       while( cpdm != NULL )
       {
          fpam = cpam;
	  cpam = cpam->next;
	  free(fpam);
       }

       /* remove air explosions from list and free memory */
       cpe = defensiveExplosions.next;
       while( cpe != NULL )
       {
          fpe = cpe;
	  cpe = cpe->next;
	  free(fpe);         
       }
       
       /* remove surface explosions from list and free memory */
       cpe = surfaceExplosions.next;
       while( cpe != NULL )
       {
          fpe = cpe;
	  cpe = cpe->next;
	  free(fpe);
       }
      
   }  
   return self;
}


unsigned currentTimeInMs()
{
   struct timeval curTime;
   gettimeofday(&curTime, NULL);
   return ((unsigned)curTime.tv_sec)*1000+curTime.tv_usec / 1000;
}

- step
{
    NXEvent dummyEvent;
    unsigned int timeNow,dTime;
     
    do
    {
       timeNow = currentTimeInMs();
       dTime = timeNow - aTimeBefore; 
       if( dTime > DELTA_TIME )
       {
          aTimeBefore = timeNow;
       
          if( defensiveMissiles.launched != 0 )
             [self missilePath];
          if( defensiveExplosions.exploding > 0 )
	     [self expDraw];
          if( offensiveMissiles.launched > 0)
             [self attackMissilesPath];
          if( surfaceExplosions.exploding > 0 )
             [self surfaceExplosionsDraw];
       }
       if( 
            defensiveMissiles.launched == 0  && 
            defensiveExplosions.exploding == 0	&&
	   offensiveMissiles.launched == 0  &&
	   surfaceExplosions.exploding  == 0    
         )
	{ 
	   if( !phaseOne )
	      [ self bonusPoints];
           [self nextLevel];
	 }   
      NXPing();
    }
    while ([NXApp peekNextEvent:NX_ALLEVENTS into:&dummyEvent] == NULL );
    return self;
}

- drawSelf:(const NXRect *)r :(int)c
{
   int i;

   PSsetgray(1.0);
   NXRectFill(r);
   PSsetgray(NX_BLACK);
   NXFrameRect(r);
	
   // draw terrain
   PSsetgray(TERRAINCOLOUR);
   PSnewpath();
   
   PSmoveto(0.0,0.0);
   PSrlineto(0.0,5.0);
   PSrlineto(50.0,0.0);
   PSrlineto(5.0,5.0);

   PSrlineto(40.0,0.0);
   PSrlineto(5.0,-5.0);

   PSrlineto(80.0,0.0);
   PSrlineto(5.0,5.0);

   PSrlineto(40.0,0.0);
   PSrlineto(5.0,5.0);
   PSrlineto(40.0,0.0);
   PSrlineto(5.0,-5.0);
   PSrlineto(40.0,0.0);
   PSrlineto(5.0,-5.0);
   PSrlineto(80.0,0.0);
   PSrlineto(5.0,5.0);
   PSrlineto(40.0,0.0);
   PSrlineto(5.0,-5.0);
   PSrlineto(50.0,0.0);
   PSrlineto(0.0,-5.0);	
   PSclosepath();
   PSfill();
   PSflushgraphics();
   
   if( displayBase )   /* display missles base if it has not been destroyed */
      [self drawLaunchPad];
      
   for(i=0;i<CITIES; i++)
      if( cityStatus[i] )
         [self drawCityBM:CBarray[i].coord];
   return self;
}

- drawCityBM:(NXPoint ) dcPt
{
   [cityBitmap composite:NX_SOVER toPoint:&dcPt];
   return self;
}

- drawLaunchPad
{
    NXPoint  Pt;
    Pt.x = 230.0;
    Pt.y = 15.0;
   [launcherBitmap composite:NX_SOVER toPoint:&Pt];
   return self;

}
- drawMissile
{
   id missileBitmap = [NXImage findImageNamed:"missileIcon.tiff"];
   NXPoint  Pt;
   Pt.x = 160.0;
   Pt.y = 190.0;
   [missileBitmap composite:NX_COPY toPoint:&Pt];
   return self;
}

- mouseDown:(NXEvent *)theEvent
{
   	NXPoint sPt;
	int pntInRect();
	MSLE_NODE_PNTR mLaunch,p;
		
	sPt = theEvent->location;
	[self convertPoint:&sPt fromView:nil];
		
	if ( defensiveMissiles.launched < MAXDEFLAUNCH  && missiles > 0  && pntInRect(&sPt))
	{
            [outMissiles setIntValue:--missiles];    
	   defensiveMissiles.launched++;
	   [self lockFocus];
            markLoc(sPt.x,sPt.y);
	   [self unlockFocus];
	   
	  
	   mLaunch = malloc(sizeof(MSLE_NODE));
		
	   mLaunch->dPos = sPt;
	   mLaunch->cPos.x = LAUNCH_X;
	   mLaunch->cPos.y = LAUNCH_Y;
	   mLaunch->next   = NULL;
	
	   // must check for zeros
	  if (ABS(sPt.x - LAUNCH_X) > ABS(sPt.y - LAUNCH_Y))
	  {
	     mLaunch->delta_x = DISP;
	     mLaunch->delta_y = DISP *(sPt.y - LAUNCH_Y) /(sPt.x - LAUNCH_X);
	  }
	  else
	  { 
	     mLaunch->delta_y = DISP;
	     mLaunch->delta_x = DISP *(sPt.x - LAUNCH_X)/ (sPt.y - LAUNCH_Y);
	  }
	
	  if ( defensiveMissiles.next == NULL) 
	     defensiveMissiles.next = mLaunch;
	  else 
	  { 
	    p = defensiveMissiles.next;
	    while (p->next != NULL )
	          p = p->next;
	    p->next = mLaunch;
	   
	  } 	
     }

     return self;
}

- expDraw
{
    EXP_PNTR  cp,pp;
    pp = cp = defensiveExplosions.next;
    [self lockFocus];
    while (cp != NULL )
    {
       if (cp->rad > MAXRADIUS )
       {
          cp->maxReached = YES;
	  cp->rad += ( cp->maxReached ) ?  -RADINC: RADINC ;
	  pp = cp; 
          cp = cp->next;
       }   
       else if ( ( cp->rad <= 0.0 ) && cp->maxReached )
       {
          if ( defensiveExplosions.next == cp )
	  {
	     pp = defensiveExplosions.next = cp->next;
	     free(cp);
	     cp = pp;
	  }   
	  else   
	  {
	     pp->next = cp->next;
	     free(cp);
	     cp = pp->next;
	  }
	  defensiveExplosions.exploding--;
       }   
       else
       {
          PSsetgray((cp->maxReached) ?  NX_WHITE : NX_BLACK );
          PSarc(cp->cPt.x,cp->cPt.y,cp->rad,0.0,360.0);
          PSstroke();
          PSflushgraphics();
 	  cp->rad += (( cp->maxReached ) ?  -RADINC: RADINC ) ;
	  pp = cp; 
	  cp = cp->next;
        }  
    }
    [self unlockFocus];  
    return self;
}

- surfaceExplosionsDraw
{
      EXP_PNTR  cp,pp;
 
    pp = cp = surfaceExplosions.next;
    [self lockFocus];
    while (cp != NULL )
    {
       if (cp->rad > MAXRADIUS )
       {
          cp->maxReached = YES;
	  cp->rad += ( cp->maxReached ) ?  -RADINC: RADINC ;
	  pp = cp; 
          cp = cp->next;
       }   
       else if ( ( cp->rad <= 0.0 ) && cp->maxReached )
       {
          if ( surfaceExplosions.next == cp )
	  {
	     pp = surfaceExplosions.next = cp->next;
	     PSnewpath();
	     PSsetgray( NX_WHITE);
	     PSmoveto(cp->cPt.x,cp->cPt.y);
	     PSrlineto(-10,0);
	     PSrlineto(0,20);
	     PSrlineto(30,0);
	     PSrlineto(0,-20);
	     PSrlineto(-10,0);
	     PSfill();
	     PSflushgraphics();
	     free(cp);
	     cp = pp;
	  }   
	  else   
	  {
	     pp->next = cp->next;
	     
	     PSnewpath();
	     PSsetgray( NX_WHITE);
	     PSmoveto(cp->cPt.x,cp->cPt.y);
	     PSrlineto(-10,0);
	     PSrlineto(0,20);
	     PSrlineto(30,0);
	     PSrlineto(0,-20);
	     PSrlineto(-10,0);
	     PSfill();
	     PSflushgraphics();

	     free(cp);
	     cp = pp->next;
	  }
	  surfaceExplosions.exploding--;
	 
       }   
       else
       {
          [self lockFocus];
          PSsetgray((cp->maxReached) ?  NX_WHITE : NX_BLACK );
          PSarc(cp->cPt.x,cp->cPt.y,cp->rad,1.0,180.0);
          PSstroke();
          PSflushgraphics();
          [self unlockFocus];
	  cp->rad += (( cp->maxReached ) ?  -RADINC: RADINC ) ;
	  pp = cp; 
	  cp = cp->next;

        }  
    }
   [self unlockFocus];
   return self;
   
  
}


- missilePath
{
    NXPoint nPos;  // new missile position
    MSLE_NODE_PNTR  cp,pp,dp;
    EXP_PNTR        nExp;
     
    pp = cp = defensiveMissiles.next;
    [self lockFocus];
    while ( cp != NULL )
    {   
       if ( cp->dPos.y <= cp->cPos.y)                     /* missile has reached it destination  */
       {	 
	  PSmoveto(LAUNCH_X,LAUNCH_Y);
	  PSlineto(cp->dPos.x,cp->dPos.y);
	  PSsetgray(NX_WHITE);
	  PSsetlinewidth(2.0);
	  PSstroke();
	  PSsetgray(NX_BLACK);
	  PSflushgraphics();
	  	      
	  if ( cp == defensiveMissiles.next )
	  {
	     defensiveMissiles.next = cp->next;
	     dp = cp; 
	     cp = defensiveMissiles.next;
	  }	  
	  else
	  {                                                           
	     pp->next = cp->next;
	     dp = cp;
	     cp = pp->next; 
	  }	 
 	  nExp = malloc(sizeof(EXP_NODE));
	  nExp->cPt = dp->dPos;
	  nExp->rad = 0.0;
	  nExp->maxReached = NO;
	  nExp->next = NULL;
		 
	  free(dp);
	  [self addToAirExplosions:nExp];
	  defensiveMissiles.launched--;
        } 		 
	else    /* calculate and plot next position */
	{
	 
	 
           nPos.x = cp->cPos.x + cp->delta_x;
	  if( cp->delta_x  < 0 )      /* make sure that point is not past  detonation point */
	   {
	       if( nPos.x < cp->dPos.x)
	          nPos.x = cp->dPos.x;
	   }
	   else
	   {
	       if( nPos.x > cp->dPos.x )
	          nPos.x = cp->dPos.x;	       
	   }  
           nPos.y = cp->cPos.y + cp->delta_y;
	   
           if ( cp->dPos.y > cp->cPos.y )
	   {
	      plotTrajectory(cp->cPos.x,cp->cPos.y,nPos.x,nPos.y);
     	      cp->cPos = nPos;
           }
	   pp = cp;
	   cp = cp->next;
	 }	
     }
     [self unlockFocus];
     return self;
}

- nextLevel
{
   int cities,i;
   
   cities = 0;
   for(i=0;i<CITIES; i++)
      if( cityStatus[ i ] )     
	 cities++;       
   
   if( cities == 0 )
      gameRunning = FALSE;
   else
   {  
      if( phaseOne)
         phaseOne = FALSE;
      else
      {
         phaseOne = TRUE;
         if( dFactor < cdiffmax )
            dFactor += cdiffinc;
         missiles = 40;
	 displayBase = TRUE;     
      }   
      // reset time  
      aTimeBefore = currentTimeInMs();
  
  
      [outMissiles setIntValue:missiles];
      [outScore setIntValue:score];
   
      [self display];
      
      // calculate attack positions
      [self selectTargets];
      
   } 
   return self;
}

- bonusPoints
{
  NXPoint cityPt;
  int  cities,i,re;
  int  vacantPos[ CITIES ],j;
  unsigned int stime,etime;
  
  cityPt.x = SCOREXPOS;
  cityPt.y = SCOREYPOS;
  cities = 0;
  for(i=0;i<CITIES; i++)
      if( cityStatus[ i ] )     
	cities++;    

  [self lockFocus];	   
  [self drawCityBM:cityPt];
  [self unlockFocus];   
  for(i=0; i <= cities; i++ )
  {  
     [self lockFocus];
     scorePhaseCities(i,i*CITYBONUS,&re);
     [self unlockFocus];
  } 

     etime = stime = currentTimeInMs();
     while( etime - stime < 100 )
        etime = currentTimeInMs();  

 
  score +=  cities * CITYBONUS;
  [outScore setIntValue:score];
  
  [self lockFocus];
  [self drawMissile];
  [self unlockFocus];

  for(i=0; i <= missiles; i++ )
  {
     [self lockFocus];
     scorePhaseMissiles(i,i*MSLBONUS,&re);
     [self unlockFocus];

     etime = stime = currentTimeInMs();
     while( etime - stime < 100 )
        etime = currentTimeInMs();
  }   
  
  score += missiles * MSLBONUS;
  [outScore setIntValue:score];
  etime = stime = currentTimeInMs();
  while( etime - stime < 1000 )
     etime = currentTimeInMs();

  if( score - lastBonusAt > BONUSMARK )
  {
     lastBonusAt += BONUSMARK;
     j = 0;
     for(i=0;i<CITIES ; i++)
        if( cityStatus[ i ] == FALSE )
	   vacantPos[j++] = i; ; 
	 
     if( j )
        cityStatus[ vacantPos[PLACECITY(j)]] = TRUE;
     else
        score += CITYBONUS;
     
  }  
  return self;
}


- selectTargets
{
  float dx,dy;
  MA_NODE_PNTR head,cp,pp;
  pp = head = cp = malloc(sizeof(MA_NODE));

  while (++offensiveMissiles.launched <= MAXATCKMSSLS ) /* attack missles init */
  {
     cp->sPos.x = RANDLAUNCH;
     cp->sPos.y = OLAUNCH_Y;
     cp->targetLoc = RANDTARGET;
     cp->dPos.x = CBarray[cp->targetLoc].coord.x + CDX;
     cp->dPos.y = CBarray[cp->targetLoc].coord.y;
     
     cp->cPos = cp->sPos;
     
     dx = cp->dPos.x - cp->sPos.x ;
     dy = cp->dPos.y - OLAUNCH_Y;
     if ( ABS(dy ) > ABS(dx))
     {
        cp->delta_y = -1.0;
	cp->delta_x = ( dx ) / ABS(dy );
     }
     else
     {
        cp->delta_y = ( dy )/ABS( dx ) ;
        cp->delta_x = ABS(dx ) / dx ;
     }
     
     cp->next = malloc(sizeof(MA_NODE));
     pp = cp;
     cp = cp->next; 
      
  }
  offensiveMissiles.launched--;
  offensiveMissiles.next = head;
  pp->next = NULL;
  free(cp);
  [redAlertSound play];  

  return self;
}

- addToAirExplosions: (EXP_PNTR )aExp
{
     EXP_PNTR ep;
      
     if ( defensiveExplosions.next == NULL )
	defensiveExplosions.next = aExp;
     else
     {		   
	ep = defensiveExplosions.next;
	while (ep->next != NULL )
	   ep = ep->next;
	ep->next = aExp;
     } 
     defensiveExplosions.exploding++;   	       
     return self;
}

- addToSurfaceExplosions:(EXP_PNTR) aExp
{
    EXP_PNTR ep;
      
    if ( surfaceExplosions.next == NULL )
       surfaceExplosions.next = aExp;
    else
    {		   
       ep = surfaceExplosions.next;
       while (ep->next != NULL )
	  ep = ep->next;
       ep->next = aExp;
    } 
    surfaceExplosions.exploding++; 
    return self;
}


- attackMissilesPath
{
    MA_NODE_PNTR head,cp,pp;
    EXP_PNTR     nExp;
    NXPoint nPt;
    NXRect rect = {{0.0, 0.0}, {1.0, 1.0}};
    id image;
    NXColor colorAtSomeLoc, colorAt();
    float alpha,color;
    
    [self lockFocus];
    head = cp = pp = offensiveMissiles.next;   /* set up pointers to point at head of attack missile list */
       while ( cp != NULL )
       {
          // calculate next point in trajectory
          nPt.x = cp->cPos.x + dFactor * cp->delta_x;    
          nPt.y = cp->cPos.y + dFactor * cp->delta_y;
	  
	  // set rect to check  if missile has been hit
          rect.origin = nPt;
          image = [[NXBitmapImageRep alloc] initData:NULL fromRect:&rect];
          colorAtSomeLoc = colorAt(image, 0, 0);
          [image free];
      
          NXConvertColorToGrayAlpha (colorAtSomeLoc, &color, &alpha);
          if ( nPt.y < cp->dPos.y)   /* attack missile has arrived at target */
          {        
             offensiveMissiles.launched--;
	     if  ( cp->targetLoc  ==  ( TARGETS - 1 ) )
	     {
	         missiles = 0;
                 [outMissiles setIntValue:missiles];
		 displayBase = FALSE; 
              }
	     else
	       cityStatus[ cp->targetLoc ] = FALSE;
	     /* erase missile trajectory */
             PSmoveto(cp->cPos.x,cp->cPos.y);
	     PSlineto(cp->sPos.x,cp->sPos.y);
	     PSsetgray(NX_WHITE);
	     PSsetlinewidth(2.0);
	     PSstroke();
	     PSsetlinewidth(1.0);
             PSflushgraphics();
	     
	      /* creat an explosion node for surface explosions */
	     nExp = malloc(sizeof(EXP_NODE));
	     nExp->cPt = cp->cPos;
	     nExp->rad = 0.0;
	     nExp->maxReached = NO;
	     nExp->next = NULL;
	     
             [self addToSurfaceExplosions:nExp];   /* add explosion to surface exp. list */

             if ( cp == head )
	     {
	        offensiveMissiles.next = head = cp->next;
	        free(cp);
	        cp = head;		
	     }   
	     else
	     {
	        pp-> next = cp->next;
	        free(cp);
	        cp = pp->next;
	     }      

          }
          else if ( ( ABS(color - NX_BLACK) < 0.005 ) && ( cp->cPos.y > 35 ))
          {
             offensiveMissiles.launched--;      // missile has been destroyed
	     score += MSLDSTRCT;
	     [outScore setIntValue:score];
	     // remove missile trajectory
             PSmoveto(cp->cPos.x,cp->cPos.y);
	     PSlineto(cp->sPos.x,cp->sPos.y);
	     PSsetgray(NX_WHITE);
	     PSsetlinewidth(2.0);
	     PSstroke();
	     PSsetlinewidth(1.0);
             PSflushgraphics();
	     
	     nExp = malloc(sizeof(EXP_NODE));
	     nExp->cPt = cp->cPos;
	     nExp->rad = 0.0;
	     nExp->maxReached = NO;
	     nExp->next = NULL;
             [self addToAirExplosions:nExp];
	     
	     // remove missile from list
             if ( cp == head )
	     {
	        offensiveMissiles.next = head = cp->next;
	        free(cp);
	        cp = pp = head;
	     }   
	     else
	     {
	        pp-> next = cp->next;
	        free(cp);
	        cp = pp->next;
	     }      
          }
          else
          {
	    plotTrajectory(cp->cPos.x,cp->cPos.y,nPt.x,nPt.y);
	    cp->cPos = nPt;
	    pp = cp;
            cp = cp->next;	     
          }	
       }
       [self unlockFocus];

       return self;
}

int pntInRect(pnt)
NXPoint *pnt;
{
      if( pnt->x  >= X_TRG && pnt->y >= Y_TRG && pnt->x <= W_TRG && pnt->y <= H_TRG )
         return(TRUE);
      else
          return(FALSE);	 
}

void stepFun (DPSTimedEntry timedE, double timeN, void *data)
{
  [(id)data step ];
}



/**************************************************************************************************/
/* all the following code has been copied from the NeXT answers code provided by NeXT */
/***************************************************************************************************/

/* Returns the color at a certain pixel location in a bitmap. */
/* This version assumes the bitmap is 1, 2, 4, or 8 bps deep  */
/* and its either grayscale or RGB. */

NXColor colorAt(NXBitmapImageRep *image, int x, int y)
{
    int sampleCnt, bps, spp, amask;
    unsigned char *planes[5], data[5];
    NXColorSpace colorSpace;

    spp = [image samplesPerPixel];
    bps = [image bitsPerSample];
    colorSpace = [image colorSpace];

#ifdef CHECK_VALIDITY
    if ((bps != 1 && bps != 2 && bps != 4 && bps != 8) ||
	   (colorSpace != NX_RGBColorSpace || 
	    colorSpace != NX_OneIsBlackColorSpace || 
	    colorSpace != NX_OneIsWhiteColorSpace)) 
	{
	    NXLogError ("colorAt() can't deal with provided image.\n");
	    return NX_COLORCLEAR;
    }
#endif
#ifdef CHECK_RANGE
    if ((x < 0) || (y < 0) || (x >= [image pixelsWide]) || 
        (y >= [image pixelsHigh])) 
    {
	    NXLogError ("Pixel out of bounds in colorAt().\n");
	    return NX_COLORCLEAR;
    }
#endif

    [image getDataPlanes:planes];
   
    amask = (1 << bps) - 1;	/* 1, 3, 15, 255 for bps = 1, 2, 4, 8 */

    /* Get the samples into the data[] array. */
    if ([image isPlanar]) 
    {
	    int pixel = x * bps;
	    int byteLoc = [image bytesPerRow] * y + (pixel >> 3);
	    int bitLoc = pixel & 7;
	    for (sampleCnt = 0; sampleCnt < spp; sampleCnt++) 
	    {
	        data[sampleCnt] = 
		 	   ((*(planes[sampleCnt]+byteLoc)) >> (8 - bitLoc - bps)) & amask;
	    }
    } 
    else 
    {
	    unsigned char *byteLoc = planes[0] + [image bytesPerRow] * y;
	    int bitLoc = x * bps * spp;
	    for (sampleCnt = 0; sampleCnt < spp; sampleCnt++) 
	    {
	        data[sampleCnt] = 
		       ((byteLoc[bitLoc >> 3]) >> (8 - (bitLoc & 7) - bps)) & amask;
	        bitLoc += bps;
	    }
    }

    /* If no alpha, set it to opaque and increment spp. */
    /* Otherwise, compute the true color values (by un-premultipling). */

    if (![image hasAlpha]) 
    {
	    data[spp] = amask;
	    spp++;
    } 
    else if (data[spp - 1] && data[spp - 1] != amask) 
    {
	    for (sampleCnt = 0; sampleCnt < spp - 1; sampleCnt++) 
	    {
	        data[sampleCnt] = 
		       (unsigned char)(0.5 + amask * 
		       ( ((float)(data[sampleCnt])) / (data[spp - 1]) ));
	    }
    }

    /* At this point data[] contains spp samples, right justified  */
    /* within the range 0..amask.  We can either return those, or  */
    /* return them in a normalized fashion (in the range 0..255,   */
    /* after shifting them over to the left by multiplying them by */
    /* 255/amask), or we can return an NXColor. The latter is less */
    /* efficient, probably, but most abstract.   */

    switch (colorSpace) 
    {
	case NX_RGBColorSpace:
	    return NXConvertRGBAToColor(((float)data[0]) / amask, 
	    							 ((float)data[1]) / amask, 
								 ((float)data[2]) / amask, 
				[image hasAlpha] ? ((float)data[3]) / amask : NX_NOALPHA);
				
	case NX_OneIsWhiteColorSpace:
	    return NXConvertGrayAlphaToColor(((float)data[0]) / amask, 
	    				[image hasAlpha] ? ((float)data[1]) / amask : NX_NOALPHA);
					
	case NX_OneIsBlackColorSpace:
	    return NXConvertGrayAlphaToColor(((float)(amask - data[0])) / amask, 
	    				[image hasAlpha] ? ((float)(amask - data[1])) / amask :
					NX_NOALPHA);
    }
}


@end
